package com.hliushi.learn.config;

import com.alibaba.fastjson.JSONObject;
import com.hliushi.learn.annotation.Log;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 执行controller中方法前打印出请求参数，并在方法执行结束后来打印出响应值
 *
 * @author llhuang10
 * @date 2021/8/19 19:13
 */
@Slf4j
@Aspect
@Component
public class AopAspect {


    @Pointcut("@annotation(com.hliushi.learn.annotation.Log)")
    public void pointcut() {
    }

    @Pointcut("@annotation(com.hliushi.learn.annotation.Log)")
    public void pointcut02() {
    }

    /**
     * .    @Around 在切点方法外环绕执行
     * 在增强的方法上 @Around("execution(* 包名,*(...))") 或使用切点@Around("pointcut()")
     * 接收参数类型为 ProceedingJoinPoint, 必须有这个参数在切面方法入参第一位
     * 返回值为Object
     * 需要执行 ProceedingJoinPoint对象的proceed方法, 在这个方法前与后面做环绕处理,
     * 可以决定何时执行和完全阻止方法的执行
     * 返回proceed方法的返回值
     * #
     * . @Around 相当于 @Before 和 @AfterReturning功能的综合
     * #
     * 可以改变方法参数, 在proceed方法执行的时候可以传入Object[]对象作为参数, 作为目标方法的实参使用
     * 如果传入Object[]参数与方法入参数量不同或类型不同, 则会抛出异常
     * 通过改变proceed()的返回值来修改目标方法的返回值
     *
     * @param pJoinPoint
     * @param aopLog
     * @return
     */
    @Around(value = "pointcut() && @annotation(aopLog)", argNames = "pJoinPoint, aopLog")
    public Object around(ProceedingJoinPoint pJoinPoint, Log aopLog) throws Throwable {

        // Before 前置通知
        this.printLogAnnotation(aopLog);
        JSONObject conditions = this.getConditions(pJoinPoint);
        log.info("请求参数为 = {}", conditions);
        Object[] args = pJoinPoint.getArgs();
        // 放行代码, 才会执行目标方法, 如果没有这行代码, 就会阻塞线程
        //
        Object result = pJoinPoint.proceed(args);

        // After 后置通知
        log.info("响应参数为: {}", result);
        // INFO  com.hliushi.learn.config.AopAspect - 请求参数为 = {"name":"hliushi"}
        // INFO  com.hliushi.learn.config.AopAspect - 响应参数为: hello hliushi
        // 请求参数为 = {"password":"123456","username":"llhuang10"}
        // username = llhuang10, password = 123456
        // 响应参数为: <200 OK,2021-08-19 20:08:36,{}>
        return result;
    }


    public void printLogAnnotation(Log aopLog) {
        String module = aopLog.module();
        int businessTypeOrdinal = aopLog.businessType().ordinal();
        String businessTypeName = aopLog.businessType().name();
        String methodDesc = aopLog.methodDesc();

        log.info("module = {}", module);
        log.info("businessTypeOrdinal = {}", businessTypeOrdinal);
        log.info("methodDesc = {}", methodDesc);
        log.info("businessTypeName = {}", businessTypeName);
    }


    /**
     * 获取切面方法的参数列表
     *
     * @return 参数列表JSONObject
     */
    private JSONObject getConditions(JoinPoint joinPoint) {
        // 获取方法参数值数组
        Object[] args = joinPoint.getArgs();
        // 获取其方法签名
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        // 获取方法参数名称数组
        String[] parameterNames = methodSignature.getParameterNames();
        AtomicInteger index = new AtomicInteger(-1);
        JSONObject jsonObject = new JSONObject();
        Arrays.stream(parameterNames).forEach(t -> {
            jsonObject.put(t, args[index.incrementAndGet()]);
        });

        return jsonObject;
    }
}
